﻿/*
 * @author: wizardc
 */

using Dou.GPU;
using Dou.Job;
using Unity.Burst;
using Unity.Collections;

namespace Demo.WorldMap.GPU
{
    /// <summary>
    /// 整理所有需要提交 GPU 的数据信息
    /// 注：
    /// 1. 不在该类中直接对流数据进行读取，只是记录流数据的添加信息，统一都在主线程中读取，这是因为当前Job会存在多份同时在子线程执行，
    ///    而流数据只有一个，多棵树（GPU和预制）都会同时去取数据，导致出现异常，而Job为了保证效率也不要使用加锁或线程安全相关的东西；
    /// 2. 因为无法直接取数据，所以视口里更精细的裁剪放到后续的Job中执行；
    /// </summary>
    [BurstCompile]
    public unsafe class DataReductionJob : ManagedJob<DataReductionJob>.IWork
    {
        // 一个数据的字节数
        private static readonly int DataStride = GPUDefine.Stride_Float4x4;
        
        public GPUDrawContainer _container;
        
        public GPUStaticRenderNode* nodePtr;
        public GPUStaticRenderLeaf* leafPtr;
        
        public NativeList<int> visibleNodeList;
        public NativeList<int> addNodeList;
        public NativeList<int> removeNodeList;
        
        public bool isDirty;
        
        [BurstCompile]
        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        [Il2CppSetOption(Option.NullChecks, false)]
        public void Execute()
        {
            isDirty = false;
            if (removeNodeList.Length > 0)
            {
                RemoveNode();
            }
            if (addNodeList.Length > 0)
            {
                addNode();
            }
        }

        [BurstCompile]
        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        [Il2CppSetOption(Option.NullChecks, false)]
        private void RemoveNode()
        {
            // 注意移除时，数据删除必须是从后向前来删除，否则如果先删除了前面的数据，再删除后面的数据时会出现异常
            // 这里仅进行数据的添加，数据排序放在后续步骤中处理
            for (int i = 0; i < removeNodeList.Length; i++)
            {
                var index = removeNodeList[i];
                ref var node = ref nodePtr[index];
                
                // TODO : 这里更新 visibleNodeList 可优化效率
                var idx = visibleNodeList.IndexOf(node.index);
                if (idx != -1)
                {
                    visibleNodeList.RemoveAt(idx);
                }
                
                var startLeafIndex = node.startLeafIndex;
                var leafCount = node.leafCount;

                for (int j = 0; j < leafCount; j++)
                {
                    var leafIndex = startLeafIndex + j;
                    ref var leaf = ref leafPtr[leafIndex];

                    var startBufferIndex = leaf.startBufferIndex;
                    var bufferCount = leaf.bufferCount;
                    
                    // 通过 resId 找到对应的最终绘制批次
                    var resId = leaf.resId;
                    var resInfo = _container.GetResInfo(resId);
                    resInfo.RemoveDataRange(startBufferIndex, bufferCount);
                    
                    isDirty = true;
                }
            }
        }

        [BurstCompile]
        [Il2CppSetOption(Option.ArrayBoundsChecks, false)]
        [Il2CppSetOption(Option.NullChecks, false)]
        private void addNode()
        {
            for (int i = 0; i < addNodeList.Length; i++)
            {
                var index = addNodeList[i];
                ref var node = ref nodePtr[index];

                visibleNodeList.Add(node.index);

                var startLeafIndex = node.startLeafIndex;
                var leafCount = node.leafCount;

                for (int j = 0; j < leafCount; j++)
                {
                    var leafIndex = startLeafIndex + j;
                    ref var leaf = ref leafPtr[leafIndex];
                    
                    var startDataIndex = leaf.startDataIndex * DataStride;
                    var dataCount = leaf.dataCount * DataStride;
                    
                    // 通过 resId 找到对应的最终绘制批次
                    var resId = leaf.resId;
                    var resInfo = _container.GetResInfo(resId);
                    
                    leaf.startBufferIndex = resInfo.dataLen;
                    leaf.bufferCount = dataCount;
                    leaf.newAdd = true; // 标记为本次新增的节点，新增节点的buffer索引是正确的，不需要做移除调整
                    
                    resInfo.AddDataRange(startDataIndex, dataCount);

                    isDirty = true;
                }
            }
        }
    }
}
